﻿using System;
using System.Collections.Generic;
using System.Linq;
using Ewk.MachineLearning.Genetic.Programming.Program;

namespace Ewk.MachineLearning.Genetic.Programming
{
    internal class NodeTreeFactory
    {
        private readonly int _maxDepth;

        public enum GenerationMethod
        {
            Full,
            Grow
        }

        private static readonly Random Random = new Random(DateTime.UtcNow.Millisecond);
        private readonly FunctionNodeFactory _functionNodeFactory;
        private readonly TerminalNodeFactory _terminalNodeFactory;

        #region Constructors

        public NodeTreeFactory(int maxDepth, TerminalNodeFactory terminalNodeFactory, FunctionNodeFactory functionNodeFactory)
        {
            _maxDepth = maxDepth;
            _terminalNodeFactory = terminalNodeFactory;
            _functionNodeFactory = functionNodeFactory;
        }

        #endregion

        public Node Create(GenerationMethod generationMethod, Type expectedOutputType)
        {
            return GenerateTree(_maxDepth, generationMethod, new List<Type> {expectedOutputType});
        }

        private Node GenerateTree(int maxDepth, GenerationMethod generationMethod, IEnumerable<Type> expectedOutputTypes)
        {
            Node root;

            if (maxDepth <= 1)
            {
                root = _terminalNodeFactory.GetRandomNode(expectedOutputTypes);
            }
            else if (generationMethod == GenerationMethod.Full)
            {
                root = _functionNodeFactory.CreateNode(expectedOutputTypes);
            }
            else
            {
                var choise = Random.Next(2);

                if (choise == 0)
                {
                    root = _functionNodeFactory.CreateNode(expectedOutputTypes);
                }
                else
                {
                    root = _terminalNodeFactory.GetRandomNode(expectedOutputTypes);
                }
            }

            var functionNode = root as FunctionNode;
            if (functionNode == null) return root;

            functionNode.ChildNodes = functionNode.FunctionDefinition.InputDefinitions
                .Select(inputDefinition =>
                        GenerateTree(maxDepth - 1, generationMethod, inputDefinition.Types));

            return root;
        }
    }
}